//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/tty/clinereader.h>
#include <buola/tty/output.h>
#include <buola/tty/input.h>
#include <buola/algorithm/comparison.h>
#include <buola/io.h>
#include <boost/regex.hpp>

#include <numeric>

namespace buola { namespace tty {

CLineReader::CLineReader(CScreen *pScreen)
    :   mIO(pScreen)
{
    mIO.sWinSizeChanged.Connect(&CLineReader::RecalcAndRedraw,this);

    mEOF=false;
    
    mHistory.mFirstNew=0;
}

CLineReader::~CLineReader()
{
}

void CLineReader::CalcFormat()
{
    //syntax highlighting would go here
}

CCursorPos CLineReader::GetCursorPos(int pCursor)
{
    CCursorPos lPos=mPrompt.mOP;
    for(int i=0;i<mTextBlocks.size();i++)
    {
        CControlledIO::SBlock &lBlock=mTextBlocks[i];
        if(pCursor>=lBlock.mIE||lBlock.mNP)
        {
            if(lBlock.mNL) lPos.r++;
            continue;
        }
        lPos.c=lBlock.mOS;
        for(int j=lBlock.mIS;j<pCursor;j++)
            lPos.c+=wcwidth(mText[j]);
        return lPos;
    }
    
    lPos.c=mTextBlocks.back().mNL?0:mTextBlocks.back().mOE;
    return lPos;
}

void CLineReader::Redraw(int pFrom)
{
    mIO.SetCursor(false);
    if(pFrom<0)
    {
        mIO.MoveCursor(CCursorPos(0,0));
        mIO.OutputBlocks(mPrompt.mBlocks.begin(),mPrompt.mBlocks.end());
        pFrom=0;
    }
    else
    {
        mIO.MoveCursor(GetCursorPos(pFrom));
    }
    mIO.OutputBlocks(mTextBlocks.begin(),mTextBlocks.end(),pFrom,{mSelect.mB,mSelect.mE});
    mIO.ClearToEnd();
    mTextEndPos=mIO.GetCursor();
    
    if(mMode&MODE_NUMARG)
    {
        std::wstring lNumArgTex=L'('+to_wstring(mNumArg)+L')';
        mIO.MoveCursor(CCursorPos(mTextEndPos.r,mIO.GetWidth()-lNumArgTex.length()-1));
        mIO.Output(wcolor(COLOR_BRIGHTRED,lNumArgTex));
    }

    if(mMode&MODE_MESSAGE)
    {
        mIO.NewLine();
        mIO.Output(mMessage);
    }

    if(mMode&MODE_ANYSEARCH)
    {
        PrintAnySearch(mResultLines);
    }
    else if(mMode&MODE_COMPLETE)
    {
        PrintCompletion();
    }

    mIO.MoveCursor(GetCursorPos(mCursor));
    mIO.SetCursor(true);
}

const std::wstring &CLineReader::GetSearchResult(int i)
{
    if(mMode&(MODE_SEARCH|MODE_REGEX))
        return mHistory.mLines[mSearch.mResults[i]];
    else //MODE_USUAL
        return mUsual.mMatches[i]->first;
}

void CLineReader::PrintCompletion()
{
    mIO.MoveCursor(GetCursorPos(mText.length()));
    mIO.ClearToEnd();
    bool lLastPage=mCompletion.mPage==mCompletion.mPages.size()-1;
    int lFirstToPrint=mCompletion.mPages[mCompletion.mPage];
    int lLastToPrint=lLastPage?mCompletion.mCount:mCompletion.mPages[mCompletion.mPage+1];
    int lTotalPrinted=0;
                                                                   
    for(int i=0;i<mCompletion.mResult.mGroups.size();i++)
    {
        CCompletion<std::wstring>::SGroup &lG=mCompletion.mResult.mGroups[i];
        SGroupInfo &lI=mCompletion.mGroups[i];

        if(!lG.mOptions.size()) continue;

        int lStart,lEnd;
        
        if(lFirstToPrint>=lTotalPrinted+lG.mOptions.size())
        {
            lTotalPrinted+=lG.mOptions.size();
            continue;
        }
        else if(lFirstToPrint>lTotalPrinted)
        {
            lStart=lFirstToPrint-lTotalPrinted;
        }
        else
        {
            lStart=0;
        }

        if(lLastToPrint<lTotalPrinted+lG.mOptions.size())
        {
            lEnd=lLastToPrint-lTotalPrinted;
        }
        else
        {
            lEnd=lG.mOptions.size();
        }

        lTotalPrinted+=lStart;

        mIO.NewLine();
        mIO.Output(wcolor(COLOR_BRIGHTBLACK,lG.mName));
        mIO.NewLine();
        int lCount=0;
        for(int i=lStart;i<lEnd;i++)
        {
            mIO.Output(wcolor(lG.mOptions[i].mColor));
            if(lTotalPrinted==mCompletion.mCursor)
                mIO.Output("\e[7m");
            mIO.Output(lG.mOptions[i].mDisplay);
            if(lTotalPrinted==mCompletion.mCursor)
                mIO.Output("\e[27m");
            mIO.Output(wcolor());
            lTotalPrinted++;
            
            if(i==lEnd-1) break;

            if(++lCount>=lI.mCols)
            {
                mIO.NewLine();
                lCount=0;
            }
            else
            {
                std::wstring lPad(lI.mWidth-lG.mOptions[i].mDisplay.length(),L' ');
                mIO.Output(lPad);
            }
        }
        
        if(lTotalPrinted>=lLastToPrint) break;
    }
    
    if(!lLastPage)
    {
        mIO.NewLine();
        mIO.Output(L"  -- more --");
    }
}

void CLineReader::PrintAnySearch(int pLines)
{
    if(mAnySearch.mPos<mAnySearch.mFirst)
        mAnySearch.mFirst=mAnySearch.mPos;
    if(mAnySearch.mPos>mAnySearch.mFirst+pLines-1)
        mAnySearch.mFirst=mAnySearch.mPos-pLines+1;
    
    RecalcAnySearch(mAnySearch.mFirst+pLines*2);
    
    if(!mAnySearch.mCount)
    {
        mIO.NewLine();
        mIO.Output(" -- no results --");
    }
    else
    {
        if(mAnySearch.mFirst+pLines>mAnySearch.mCount)
        {
            mAnySearch.mFirst=std::max(0,(int)mAnySearch.mCount-pLines);
        }
    
        int lLast=std::min((int)mAnySearch.mCount,mAnySearch.mFirst+pLines);
        
        for(int i=mAnySearch.mFirst;i<lLast;i++)
        {
            mIO.NewLine();
            const std::wstring &lLine=GetSearchResult(i);
            if(mAnySearch.mPos==i)
            {
                mIO.Output("\e[7m");
                if(lLine.length()>mIO.GetWidth())
                    mIO.Output(lLine.substr(0,mIO.GetWidth()));
                else
                    mIO.Output(lLine);
                if(lLine.length()<mIO.GetWidth())
                    mIO.Output(std::string(mIO.GetWidth()-lLine.length(),' '));
                mIO.Output("\e[27m");
            }
            else
            {
                if(lLine.length()>mIO.GetWidth())
                    mIO.Output(lLine.substr(0,mIO.GetWidth()));
                else
                    mIO.Output(lLine);
                mIO.ClearToEOL();
            }
        }
    }
    
    mIO.ClearToEnd();
}

void CLineReader::SetPrompt(const std::wstring &pText)
{
    mPrompt.mSet=pText;
}

void CLineReader::RecalcPrompt()
{
    if(mMode&MODE_SEARCH)
        mPrompt.mEffective=mPrompt.mSet+L'\037'+wcolor(COLOR_BRIGHTRED)+L"\037(search) \037"+wcolor()+L'\037';
    else if(mMode&MODE_REGEX)
        mPrompt.mEffective=mPrompt.mSet+L'\037'+wcolor(COLOR_BRIGHTRED)+L"\037(regex search) \037"+wcolor()+L'\037';
    else if(mMode&MODE_USUAL)
        mPrompt.mEffective=mPrompt.mSet+L'\037'+wcolor(COLOR_BRIGHTRED)+L"\037(most usual) \037"+wcolor()+L'\037';
    else
        mPrompt.mEffective=mPrompt.mSet;

    mPrompt.mBlocks.clear();
    mPrompt.mOP=mIO.BreakString(mPrompt.mEffective,mPrompt.mBlocks,0,CCursorPos(0,0));
}

void CLineReader::RecalcText()
{
    CalcFormat();
    mTextBlocks.clear();
    mIO.BreakString(mText,mTextBlocks,0,mPrompt.mOP);
}

void CLineReader::RecalcCompletion()
{
    mCompletion.mGroups.clear();
    mCompletion.mPages.clear();
    mCompletion.mPage=0;
    mCompletion.mCount=0;
    mCompletion.mPrefix.clear();
    mCompletion.mCursor=-1;
    
    int lRowsInPage=0;
    mCompletion.mPages.push_back(0);
    
    for(int i=0;i<mCompletion.mResult.mGroups.size();i++)
    {
        CCompletion<std::wstring>::SGroup &lG=mCompletion.mResult.mGroups[i];
        int lMaxWidth=0;
        
        SGroupInfo lInfo;
        lInfo.mFirstIndex=mCompletion.mCount;

        for(int j=0;j<lG.mOptions.size();j++)
        {
            CCompletion<std::wstring>::SOption &lO=lG.mOptions[j];
            lMaxWidth=std::max(lMaxWidth,(int)lO.mDisplay.length());
            mCompletion.mCount++;
            
            if(mCompletion.mCount==1)
            {
                mCompletion.mPrefix=lO.mText;
            }
            else
            {
                int lLen=std::min(mCompletion.mPrefix.length(),lO.mText.length());
                std::wstring::const_iterator lE=std::mismatch(mCompletion.mPrefix.begin(),mCompletion.mPrefix.begin()+lLen,
                                                          lO.mText.begin()).first;
                if(lE!=mCompletion.mPrefix.end())
                    mCompletion.mPrefix.resize(lE-mCompletion.mPrefix.begin());
            }
        }
        
        if(!lG.mSorted)
        {
            std::sort(lG.mOptions.begin(),lG.mOptions.end());
            lG.mSorted=true;
        }
        
        lInfo.mCols=std::max(mIO.GetWidth()/(lMaxWidth+1),1);
        lInfo.mWidth=mIO.GetWidth()/lInfo.mCols;
        int lRows=div_round_up(lG.mOptions.size(),(std::size_t)lInfo.mCols);
        int lPrinted=0;
        
        while(lRows>0)
        {
            int lRowsLeft=mResultLines-lRowsInPage;
            if(lRows+1>lRowsLeft||(lRows+1==lRowsLeft&&i<mCompletion.mResult.mGroups.size()-1)) //it doesn't fit
            {
                int lCanPrint=std::max(0,lRowsLeft-2);
                lRows-=lCanPrint;
                lPrinted+=lCanPrint*lInfo.mCols;
                mCompletion.mPages.push_back(lInfo.mFirstIndex+lPrinted);
                lRowsInPage=0;
            }
            else //it fits
            {
                lRowsInPage+=lRows+1;
                break;
            }
        }

        mCompletion.mGroups.push_back(lInfo);
    }
}

struct AUsualSorter
{
    template<typename tI>
    bool operator()(const tI &p1,const tI &p2)
    {
        return p1->second>p2->second;
    }
};

void CLineReader::RecalcUsual()
{
    if(mUsual.mMap.empty())
        mUsual.mLastUpdate=0;

    if(mUsual.mLastUpdate==mHistory.mLines.size()&&mText==mUsual.mLastText)
        return;
    
    for(;mUsual.mLastUpdate<mHistory.mLines.size();mUsual.mLastUpdate++)
        mUsual.mMap[mHistory.mLines[mUsual.mLastUpdate]]++;

    mUsual.mMatches.resize(mUsual.mMap.size());
    int j=0;
    if(mText.empty())
    {
        for(auto i=mUsual.mMap.begin();i!=mUsual.mMap.end();++i)
            mUsual.mMatches[j++]=i;
    }
    else
    {
        for(auto i=mUsual.mMap.begin();i!=mUsual.mMap.end();++i)
            if(starts_with(i->first,mText))
                mUsual.mMatches[j++]=i;
        mUsual.mMatches.resize(j);
    }

    std::sort(mUsual.mMatches.begin(),mUsual.mMatches.end(),AUsualSorter());

    mUsual.mLastText=mText;
    mAnySearch.mCount=mUsual.mMatches.size();
}

void CLineReader::RecalcSearch(int pNum)
{
    if(mSearch.mResults.size()>=pNum) return;
    
    for(mSearch.mSearched--;mSearch.mSearched>=0;mSearch.mSearched--)
    {
        const std::wstring &lLine=mHistory.mLines[mSearch.mSearched];
        if(lLine.find(mText)==std::wstring::npos) continue;
        if(mSearch.mAlready.find(lLine)!=mSearch.mAlready.end()) continue;
            
        mSearch.mResults.push_back(mSearch.mSearched);
        mSearch.mAlready.insert(lLine);
        if(mSearch.mResults.size()>=pNum) break;
    }
    
    mAnySearch.mCount=mSearch.mResults.size();
}

void CLineReader::RecalcRegex(int pNum)
{
    if(mSearch.mResults.size()>=pNum) return;
    
    try
    {
        boost::wregex lRegex(mText);
    
        for(mSearch.mSearched--;mSearch.mSearched>=0;mSearch.mSearched--)
        {
            const std::wstring &lLine=mHistory.mLines[mSearch.mSearched];
            if(!regex_search(lLine,lRegex)) continue;
            if(mSearch.mAlready.find(lLine)!=mSearch.mAlready.end()) continue;
            
            mSearch.mResults.push_back(mSearch.mSearched);
            mSearch.mAlready.insert(lLine);
            if(mSearch.mResults.size()>=pNum) break;
        }
    }
    catch(...)
    {}
    
    mAnySearch.mCount=mSearch.mResults.size();
}

void CLineReader::RecalcAnySearch(int pNum)
{
    if(mMode&MODE_SEARCH)
        RecalcSearch(pNum);
    else if(mMode&MODE_REGEX)
        RecalcRegex(pNum);
    else
        RecalcUsual();
}

void CLineReader::RecalcAndRedraw()
{
    mResultLines=mIO.GetHeight()/2;
    RecalcPrompt();
    RecalcText();
    Redraw();
}

struct ACompletionFilter
{
    ACompletionFilter(const std::wstring &pPart)
        :   mPart(pPart)
    {}
    
    bool operator()(const CCompletion<std::wstring>::SOption &pI)
    {
        return !starts_with(pI.mText,mPart);
    }
    
    std::wstring mPart;
};

void CLineReader::UpdateCompletion()
{
    std::wstring lPart(mText,mCompletion.mResult.mB,mCompletion.mResult.mE-mCompletion.mResult.mB+mText.length()-mCompletion.mLastText.length());
    
    //do incremental completion
    for(int i=0;i<mCompletion.mResult.mGroups.size();i++)
    {
        CCompletion<std::wstring>::SGroup &lG=mCompletion.mResult.mGroups[i];
        
        lG.mOptions.erase(std::remove_if(lG.mOptions.begin(),lG.mOptions.end(),ACompletionFilter(lPart)),lG.mOptions.end());
    }
    
    RecalcCompletion();

    if(!mCompletion.mCount||(mCompletion.mCount==1&&lPart==mCompletion.mPrefix))
        mMode&=~MODE_COMPLETE;

    Changed(CHANGE_COMPLETE);
}

void CLineReader::ChangedNone()
{
    mChanged.mType=CHANGE_NONE;
    mChanged.mFrom=mText.length();
}

void CLineReader::Changed(EChange pChange)
{
    mChanged.mType|=pChange;
}

void CLineReader::Changed(EChange pChange,int pFrom)
{
    mChanged.mType|=pChange;
    
    if(pFrom<mChanged.mFrom||mChanged.mFrom==-1)
        mChanged.mFrom=pFrom;
}

void CLineReader::CommandHistoryBack()
{
    if(mHistory.mPos>0)
    {
        if(mHistory.mPos==mHistory.mLines.size())
            mHistory.mCurrent=mText;
        mHistory.mPos=std::max(mHistory.mPos-mNumArg,0);
        mText=mHistory.mLines[mHistory.mPos];
        Changed(CHANGE_TEXT,0);
        mCursor=mText.length();
        mSelect.mB=0;
        mSelect.mE=0;
    }
}

void CLineReader::CommandHistoryForward()
{
    if(mHistory.mPos<mHistory.mLines.size())
    {
        mHistory.mPos=std::min(mHistory.mPos+mNumArg,(int)mHistory.mLines.size());
        if(mHistory.mPos==mHistory.mLines.size())
            mText=mHistory.mCurrent;
        else
            mText=mHistory.mLines[mHistory.mPos];
        Changed(CHANGE_TEXT,0);
        mCursor=mText.length();
        mSelect.mB=0;
        mSelect.mE=0;
    }
}

void CLineReader::CommandHistoryFirst()
{
    if(mHistory.mPos>0)
    {
        if(mHistory.mPos==mHistory.mLines.size())
            mHistory.mCurrent=mText;
        mHistory.mPos=0;
        mText=mHistory.mLines[mHistory.mPos];
        Changed(CHANGE_TEXT,0);
        mCursor=mText.length();
        mSelect.mB=0;
        mSelect.mE=0;
    }
}

void CLineReader::CommandHistoryLast()
{
    if(mHistory.mPos<mHistory.mLines.size())
    {
        mHistory.mPos=mHistory.mLines.size();
        mText=mHistory.mCurrent;
        Changed(CHANGE_TEXT,0);
        mCursor=mText.length();
        mSelect.mB=0;
        mSelect.mE=0;
    }
}

void CLineReader::CommandMoveCursor(int pPos,bool pSelect)
{
    if(pPos<0) 
        pPos=0;
    else if(pPos>mText.length())
        pPos=mText.length();
    
    if(pSelect)
    {
        if(pPos!=mCursor)
        {
            int lOldB=mSelect.mB;
            if(mSelect.mB==mCursor)
                mCursor=mSelect.mE;
            else if(mSelect.mE==mCursor)
                mCursor=mSelect.mB;

            std::tie(mSelect.mB,mSelect.mE)=std::minmax(mCursor,pPos);
        
            mCursor=pPos;
            Changed(CHANGE_SELECTION,std::min(lOldB,mSelect.mB));
        }
    }
    else
    {
        CommandDeselect();
        if(pPos!=mCursor)
        {
            mCursor=pPos;
            Changed(CHANGE_CURSOR);
        }
    }
}

void CLineReader::CommandCharLeft(bool pSelect)
{
    CommandMoveCursor(mCursor-mNumArg,pSelect);
}

void CLineReader::CommandCharRight(bool pSelect)
{
    CommandMoveCursor(mCursor+mNumArg,pSelect);
}

void CLineReader::CommandWordLeft(bool pSelect)
{
    if(mCursor==0) return;
    
    bool lChar=false;
    int i;
    
    for(i=mCursor-1;i>=0;i--)
    {
        if(!iswspace(mText[i]))
            lChar=true;
        else if(lChar)
            break;
    }
    
    CommandMoveCursor(i+1,pSelect);
}

void CLineReader::CommandWordRight(bool pSelect)
{
    if(mCursor==mText.length()) return;
    
    bool lSpace=false;
    int i;
    
    for(i=mCursor+1;i<mText.length();i++)
    {
        if(iswspace(mText[i]))
            lSpace=true;
        else if(lSpace)
            break;
    }

    CommandMoveCursor(i,pSelect);
}

void CLineReader::CommandHome(bool pSelect)
{
    CommandMoveCursor(0,pSelect);
}

void CLineReader::CommandEnd(bool pSelect)
{
    CommandMoveCursor(mText.length(),pSelect);
}

void CLineReader::CommandDeselect()
{
    if(mSelect.mB!=mSelect.mE)
    {
        Changed(CHANGE_SELECTION,mSelect.mB);
        mSelect.mB=0;
        mSelect.mE=0;
    }
}

void CLineReader::CommandDeleteSelection()
{
    if(mSelect.mB!=mSelect.mE)
    {
        mText.erase(mSelect.mB,mSelect.mE-mSelect.mB);
        mSelect.mE=mSelect.mB;
        mCursor=mSelect.mB;
        Changed(CHANGE_TEXT,mCursor);
    }
}

void CLineReader::CommandDelete()
{
    if(mSelect.mB!=mSelect.mE)
    {
        CommandDeleteSelection();
    }
    else if(mCursor<mText.length())
    {
        mText.erase(mCursor,mNumArg);
        Changed(CHANGE_TEXT,mCursor);
    }
}

void CLineReader::CommandBackspace()
{
    if(mSelect.mB!=mSelect.mE)
    {
        CommandDeleteSelection();
    }
    else if(mCursor>0)
    {
        bool lLeadingSpace=true;
        for(int i=0;i<mCursor;i++)
        {
            if(mText[i]!=L' ')
            {
                lLeadingSpace=false;
                break;
            }                    
        }
        if(lLeadingSpace)
        {
            int lBackTo=0;
            for(int i=0;i<mIndentPositions.size();i++)
            {
                if(mIndentPositions[i]<mCursor)
                    lBackTo=mIndentPositions[i];
            }
            mText.erase(mText.begin()+lBackTo,mText.begin()+mCursor);
            mCursor=lBackTo;
        }
        else
        {
            mCursor--;
            mText.erase(mText.begin()+mCursor);
        }
        Changed(CHANGE_TEXT,mCursor);
    }
    
}

void CLineReader::CommandRemoveToBOL()
{
    if(mCursor>0)
    {
        CommandDeselect();
        mText.erase(mText.begin(),mText.begin()+mCursor);
        mCursor=0;
        Changed(CHANGE_TEXT,0);
    }
}

void CLineReader::CommandRemoveToEOL()
{
    if(mCursor<mText.length())
    {
        CommandDeselect();
        mText.erase(mText.begin()+mCursor,mText.end());
        Changed(CHANGE_TEXT,mCursor);
    }
}

void CLineReader::CommandEnter()
{
    CommandDeselect();
    mCursor=mText.length();
    mFinishLine=true;
    Changed(CHANGE_CURSOR);
}

void CLineReader::CommandCancel()
{
    CommandDeselect();
    mIO.MoveCursor(mTextEndPos);
    mIO.Output(L"^C\n");
    mIO.ClearToEnd();
    mIO.End();
    throw XInternal("interrupt");
}

void CLineReader::CommandEOF()
{
    if(mText.empty())
    {
        mEOF=true;
        mFinishLine=true;
    }
}

void CLineReader::CommandComplete()
{
    CommandDeselect();

    mCompletion.mCursor=-1;
    
    if(mMode&MODE_COMPLETE)
    {
        Changed(CHANGE_COMPLETE);
        //mLastText always refers to the last text that was used when calling sComplete
        
        if(mCompletion.mLastText==mText)
        {
            mCompletion.mPage++;
            if(mCompletion.mPage>=mCompletion.mPages.size())
                mCompletion.mPage=0;
            return;
        }
        else
        {
            if(mCompletion.mCount>=1)
            {
                int lOldLength=mText.length();
                mText=mText.substr(0,mCompletion.mResult.mB)+mCompletion.mPrefix+mCompletion.mLastText.substr(mCompletion.mResult.mE);
                mCursor=mCompletion.mResult.mB+mCompletion.mPrefix.size();
                Changed(CHANGE_TEXT,mCompletion.mResult.mB);

                if(mCompletion.mCount==1)
                {
                    mMode&=~MODE_COMPLETE;
                    return;
                }
                else
                {
                    mCompletion.mPage++;
                    if(mCompletion.mPage>=mCompletion.mPages.size()||lOldLength!=mText.length())
                        mCompletion.mPage=0;
                    return;
                }
            }
            
            mMode&=~MODE_COMPLETE;
        }
    }
    
    mCompletion.mLastText=mText;
    mCompletion.mResult.mGroups.clear();
    
    //get the completion through the callback
    if(!sCompleteW.Empty())
    {
        if(!sCompleteW(mText,mCursor,mCompletion.mResult)) return;
    }
    else if(!sComplete.Empty())
    {
        std::string lText=utf8(mText.substr(0,mCursor));
        int lCursor=lText.length();
        lText+=utf8(mText.substr(mCursor));
        
        CCompletion<std::string> lResult;
        
        if(!sComplete(lText,lCursor,lResult)) return;
        mCompletion.mResult.mB=utf32(lText.substr(0,lResult.mB)).size();
        mCompletion.mResult.mE=mCompletion.mResult.mB+
                       utf32(lText.substr(lResult.mB,lResult.mE-lResult.mB)).size();

        for(int i=0;i<lResult.mGroups.size();i++)
        {
            CCompletion<std::string>::SGroup &lG=lResult.mGroups[i];
            CCompletion<std::wstring>::SGroup lWG;
            
            lWG.mName=utf32(lG.mName);
            for(int i=0;i<lG.mOptions.size();i++)
            {
                CCompletion<std::string>::SOption &lO=lG.mOptions[i];
                lWG.mOptions.emplace_back(utf32(lO.mText),utf32(lO.mDisplay),lO.mColor);
            }

            mCompletion.mResult.mGroups.push_back(std::move(lWG));
        }
    }
    else
        return;
    
    RecalcCompletion();

    if(mCompletion.mCount)
    {
        mText=mText.substr(0,mCompletion.mResult.mB)+mCompletion.mPrefix+mText.substr(mCompletion.mResult.mE);
        mCursor=mCompletion.mResult.mB+mCompletion.mPrefix.size();
        Changed(CHANGE_TEXT,mCompletion.mResult.mB);

        if(mCompletion.mCount==1)
            return;
        
        mMode|=MODE_COMPLETE;
        Changed(CHANGE_COMPLETE);
    }
    else
    {
        mMode|=MODE_MESSAGE;
        mMessage=L"  -- no options found --";
        Changed(CHANGE_MESSAGE);
    }
}

void CLineReader::CommandCompleteMove(EKeyCode pKey)
{
    if(mCompletion.mCursor<0)
    {
        mCompletion.mCursor=mCompletion.mPages[mCompletion.mPage];
    }
    else
    {
        int lGroup;
        for(lGroup=mCompletion.mGroups.size()-1;lGroup>=1;lGroup--)
        {
            if(mCompletion.mCursor>=mCompletion.mGroups[lGroup].mFirstIndex)
                break;
        }
        SGroupInfo &lI=mCompletion.mGroups[lGroup];
        int lIndex=mCompletion.mCursor-lI.mFirstIndex;
        int lSize=mCompletion.mResult.mGroups[lGroup].mOptions.size();
        switch(pKey)
        {
        case KEY_UP:
            if(lIndex>=lI.mCols)
                mCompletion.mCursor-=lI.mCols;
            else
                mCompletion.mCursor=lI.mFirstIndex-1;
            break;
        case KEY_DOWN:
            if(lIndex+lI.mCols<lSize)
                mCompletion.mCursor+=lI.mCols;
            else
                mCompletion.mCursor=lI.mFirstIndex+lSize;
            break;
        case KEY_LEFT:
            mCompletion.mCursor--;
            break;
        case KEY_RIGHT:
            mCompletion.mCursor++;
            break;
        case KEY_HOME:
            mCompletion.mCursor=0;
            break;
        case KEY_END:
            mCompletion.mCursor=mCompletion.mCount-1;
            break;
        case KEY_PGUP:
            if(mCompletion.mPage==0)
                mCompletion.mCursor=0;
            else
                mCompletion.mCursor=mCompletion.mPages[mCompletion.mPage]-1;
            break;
        case KEY_PGDN:
            if(mCompletion.mPage==mCompletion.mPages.size()-1)
                mCompletion.mCursor=mCompletion.mCount-1;
            else
                mCompletion.mCursor=mCompletion.mPages[mCompletion.mPage+1];
            break;
        default:
            break;
        }
        
        if(mCompletion.mCursor<0)
            mCompletion.mCursor=0;
        else if(mCompletion.mCursor>=mCompletion.mCount)
            mCompletion.mCursor=mCompletion.mCount-1;
        //recalculate page
        for(mCompletion.mPage=0;mCompletion.mPage<mCompletion.mPages.size()-1;mCompletion.mPage++)
        {
            if(mCompletion.mPages[mCompletion.mPage+1]>mCompletion.mCursor)
                break;
        }
    }
}

void CLineReader::CommandCompleteAccept()
{
    int lGroup;
    for(lGroup=mCompletion.mGroups.size()-1;lGroup>=1;lGroup--)
    {
        if(mCompletion.mCursor>=mCompletion.mGroups[lGroup].mFirstIndex)
            break;
    }
    SGroupInfo &lI=mCompletion.mGroups[lGroup];
    int lIndex=mCompletion.mCursor-lI.mFirstIndex;

    mText=mText.substr(0,mCompletion.mResult.mB)+mCompletion.mResult.mGroups[lGroup].mOptions[lIndex].mText+
                                                    mCompletion.mLastText.substr(mCompletion.mResult.mE);
    mCursor=mCompletion.mResult.mB+mCompletion.mResult.mGroups[lGroup].mOptions[lIndex].mText.size();
    Changed(CHANGE_TEXT,mCompletion.mResult.mB);
    mMode&=~MODE_COMPLETE;
}

void CLineReader::CommandExpand()
{
    if(sExpandW.Empty()) return;
    
    std::wstring lText=mText;
    int lCursor=mCursor;
    if(!sExpandW(lText,lCursor)) return;
    
    mText=lText;
    mCursor=lCursor;
    
    Changed(CHANGE_TEXT,0);
}

void CLineReader::CommandCutLine()
{
    if(mSelect.mB!=mSelect.mE)
    {
        mCutBuffer=mText.substr(mSelect.mB,mSelect.mE-mSelect.mB);
        CommandDeleteSelection();
    }
    else
    {
        mCutBuffer=mText;
        mText.clear();
        mCursor=0;
        Changed(CHANGE_TEXT,0);
        mHistory.mPos=mHistory.mLines.size();
    }
}

void CLineReader::CommandPasteLine()
{
    CommandDeleteSelection();
    for(int i=0;i<mNumArg;i++)
        mText.insert(mCursor,mCutBuffer);
    Changed(CHANGE_TEXT,mCursor);
    mCursor+=mNumArg*mCutBuffer.length();
}

void CLineReader::CommandSearch()
{
    mMode&=(MODE_ANYSEARCH|MODE_COMPLETE);
    mMode|=MODE_SEARCH;
    mSearch.mResults.clear();
    mSearch.mAlready.clear();
    mSearch.mSearched=mHistory.mLines.size();
    mAnySearch.mFirst=0;
    mAnySearch.mPos=0;
    Changed(CHANGE_PROMPT);
}

void CLineReader::CommandRegex()
{
    mMode&=(MODE_ANYSEARCH|MODE_COMPLETE);
    mMode|=MODE_REGEX;
    mSearch.mResults.clear();
    mSearch.mAlready.clear();
    mSearch.mSearched=mHistory.mLines.size();
    mAnySearch.mFirst=0;
    mAnySearch.mPos=0;
    Changed(CHANGE_PROMPT);
}

void CLineReader::CommandSearchMove(int pMove)
{
    if(!mAnySearch.mCount) return;

    int lPos=mAnySearch.mPos+pMove;
    if(lPos<0)
        lPos=0;
    else if(lPos>=mAnySearch.mCount)
        lPos=mAnySearch.mCount-1;
    
    if(lPos==mAnySearch.mPos) return;
    mAnySearch.mPos=lPos;
    Changed(CHANGE_SEARCHPOS);
}

void CLineReader::CommandSearchAccept(bool pExec,bool pCursorToStart)
{
    bool lFound=false;
    
    if(mAnySearch.mPos<mAnySearch.mCount)
    {
        mText=GetSearchResult(mAnySearch.mPos);
        lFound=true;
    }

    mMode&=~MODE_ANYSEARCH;
    Changed(CHANGE_PROMPT);

    if(pCursorToStart)
        mCursor=0;
    else
        mCursor=mText.length();
    
    if(pExec&&lFound)
    {
        CommandEnter();
    }
}

void CLineReader::CommandInsertChar(wchar_t pCh)
{
    CommandDeleteSelection();
    mText.insert(mCursor,mNumArg,pCh);
    Changed(CHANGE_TEXT|CHANGE_INSERTCHAR,mCursor);
    mCursor+=mNumArg;
}

void CLineReader::CommandNumArg(int pNum)
{
    mNumArg=pNum;
    mMode|=MODE_NUMARG;
    
    Changed(CHANGE_NUMARG);
}

void CLineReader::CommandUsual()
{
    mMode&=(MODE_ANYSEARCH|MODE_COMPLETE);
    mMode|=MODE_USUAL;
    
    mAnySearch.mFirst=0;
    mAnySearch.mPos=0;
    Changed(CHANGE_PROMPT);
}

void CLineReader::ProcessChar(wchar_t pCh)
{
    if(mMode&MODE_NUMARG)
        ProcessCharNumArg(pCh);
    else if(mMode&MODE_COMPLETE)
        ProcessCharComplete(pCh);
    else if(mMode&MODE_ANYSEARCH)
        ProcessCharSearch(pCh);
    else
        ProcessCharNormal(pCh);
}

void CLineReader::ProcessCharNumArg(wchar_t pCh)
{
    bool lNumArg=false;
    
    if(is_key_char(pCh))
    {
        EKeyCode lCode=get_key_code(pCh);
        EKeyMod lMod=get_key_mod(pCh);
        
        if(lCode>=KEY_0&&lCode<=KEY_9&&lMod&KEYMOD_ALT)
        {
            mNumArg=10*mNumArg+lCode-KEY_0;
            lNumArg=true;
        }
        else if(lCode==KEY_C&&lMod&KEYMOD_CONTROL)
        {
            mMode&=~MODE_NUMARG;
            mNumArg=1;
            lNumArg=true;
        }
    }
    else if(pCh>='0'&&pCh<='9')
    {
        mNumArg=10*mNumArg+pCh-'0';
        lNumArg=true;
    }

    if(!lNumArg)
    {
        mMode&=~MODE_NUMARG;
        ProcessChar(pCh);
        mNumArg=1;
    }
    
    Changed(CHANGE_NUMARG);
}

void CLineReader::ProcessCharComplete(wchar_t pCh)
{
    bool lComplete=false;
    
    if(is_key_char(pCh))
    {
        EKeyCode lCode=get_key_code(pCh);
        EKeyMod lMod=get_key_mod(pCh);
        
        switch(lCode)
        {
        case KEY_LEFT:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_RIGHT:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_UP:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_DOWN:
            CommandCompleteMove(lCode);
            lComplete=true;
            break;
        case KEY_PGUP:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_PGDN:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_HOME:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_END:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteMove(lCode);
                lComplete=true;
            }
            break;
        case KEY_ENTER:
            if(mCompletion.mCursor>=0)
            {
                CommandCompleteAccept();
                lComplete=true;
            }
            break;
        case KEY_TAB:
            if(!(lMod&KEYMOD_SHIFT))
            {
                CommandComplete();
                lComplete=true;
            }
            break;
        case KEY_C:
            if(lMod&KEYMOD_CONTROL&&mCompletion.mCursor>=0)
            {
                mMode&=~MODE_COMPLETE;
                lComplete=true;
            }
            break;
        default:
            break;
        }
    }
    else if(pCh>=32)
    {
        CommandInsertChar(pCh);
        UpdateCompletion();
        lComplete=true;
    }

    if(!lComplete)
    {
        mMode&=~MODE_COMPLETE;
        ProcessChar(pCh);
        mNumArg=1;
    }
    
    Changed(CHANGE_COMPLETE);
}

void CLineReader::ProcessCharSearch(wchar_t pCh)
{
    if(is_key_char(pCh))
    {
        EKeyCode lCode=get_key_code(pCh);
        EKeyMod lMod=get_key_mod(pCh);

        switch(lCode)
        {
        case KEY_LEFT:
            if(lMod&KEYMOD_CONTROL)
                CommandWordLeft(lMod&KEYMOD_SHIFT);
            else
                CommandCharLeft(lMod&KEYMOD_SHIFT);
            break;
        case KEY_RIGHT:
            if(lMod&KEYMOD_CONTROL)
                CommandWordRight(lMod&KEYMOD_SHIFT);
            else
                CommandCharRight(lMod&KEYMOD_SHIFT);
            break;
        case KEY_UP:
            CommandSearchMove(-1);
            break;
        case KEY_DOWN:
            CommandSearchMove(1);
            break;
        case KEY_PGUP:
            CommandSearchMove(-mResultLines+1);
            break;
        case KEY_PGDN:
            CommandSearchMove(mResultLines-1);
            break;
        case KEY_HOME:
            CommandHome(lMod&KEYMOD_SHIFT);
            break;
        case KEY_END:
            CommandEnd(lMod&KEYMOD_SHIFT);
            break;
        case KEY_DEL:
            CommandDelete();
            break;
        case KEY_BACKSPACE:
            CommandBackspace();
            break;
        case KEY_ENTER:
            CommandSearchAccept(true,false);
            break;
        case KEY_TAB:
            CommandSearchAccept(false,lMod&KEYMOD_SHIFT);
            break;
        case KEY_0:
        case KEY_1:
        case KEY_2:
        case KEY_3:
        case KEY_4:
        case KEY_5:
        case KEY_6:
        case KEY_7:
        case KEY_8:
        case KEY_9:
            if(lMod&KEYMOD_ALT)
                CommandNumArg(lCode-KEY_0);
            break;
        case KEY_A:
            if(lMod&KEYMOD_CONTROL)
                CommandHome(false);
            else if(lMod&KEYMOD_ALT)
                CommandHome(true);
            break;
        case KEY_B:
            if(lMod&KEYMOD_CONTROL)
                CommandCharLeft(false);
            else if(lMod&KEYMOD_ALT)
                CommandCharLeft(true);
            break;
        case KEY_C:
            if(lMod&KEYMOD_CONTROL)
            {
                mMode&=~MODE_ANYSEARCH;
                Changed(CHANGE_PROMPT);
            }
            break;
        case KEY_E:
            if(lMod&KEYMOD_CONTROL)
                CommandEnd(false);
            else if(lMod&KEYMOD_ALT)
                CommandEnd(true);
            break;
        case KEY_F:
            if(lMod&KEYMOD_CONTROL)
                CommandCharRight(false);
            else if(lMod&KEYMOD_ALT)
                CommandCharRight(true);
            break;
        case KEY_J:
            if(lMod&KEYMOD_CONTROL)
                CommandSearchAccept(true,false);
            break;
        case KEY_K:
            if(lMod&KEYMOD_CONTROL)
                CommandRemoveToEOL();
            break;
        case KEY_N:
            CommandSearchMove(1);
            break;
        case KEY_P:
            CommandSearchMove(-1);
            break;
        case KEY_R:
            if(lMod&KEYMOD_CONTROL)
                CommandSearch();
            break;
        case KEY_T:
            if(lMod&KEYMOD_CONTROL)
                CommandRegex();
            break;
        case KEY_U:
            if(lMod&KEYMOD_CONTROL)
                CommandUsual();
            break;
        case KEY_V:
            if(lMod&KEYMOD_CONTROL)
                CommandPasteLine();
            break;
        case KEY_X:
            if(lMod&KEYMOD_CONTROL)
                CommandCutLine();
            break;
        default:
            break;
        }
    }
    else if(pCh>=32)
    {
        CommandInsertChar(pCh);
    }

    if(mChanged.mType&CHANGE_TEXT)
    {
        if(mMode&(MODE_SEARCH|MODE_REGEX))
        {
            mSearch.mResults.clear();
            mSearch.mAlready.clear();
            mSearch.mSearched=mHistory.mLines.size();
        }
    
        mAnySearch.mFirst=0;
        mAnySearch.mPos=0;
    }
}

void CLineReader::ProcessCharNormal(wchar_t pCh)
{
    if(is_key_char(pCh))
    {
        EKeyCode lCode=get_key_code(pCh);
        EKeyMod lMod=get_key_mod(pCh);
        
        switch(lCode)
        {
        case KEY_LEFT:
            if(lMod&KEYMOD_CONTROL)
                CommandWordLeft(lMod&KEYMOD_SHIFT);
            else
                CommandCharLeft(lMod&KEYMOD_SHIFT);
            break;
        case KEY_RIGHT:
            if(lMod&KEYMOD_CONTROL)
                CommandWordRight(lMod&KEYMOD_SHIFT);
            else
                CommandCharRight(lMod&KEYMOD_SHIFT);
            break;
        case KEY_UP:
            CommandHistoryBack();
            break;
        case KEY_DOWN:
            CommandHistoryForward();
            break;
        case KEY_PGUP:
            CommandHistoryFirst();
            break;
        case KEY_PGDN:
            CommandHistoryLast();
            break;
        case KEY_HOME:
            CommandHome(lMod&KEYMOD_SHIFT);
            break;
        case KEY_END:
            CommandEnd(lMod&KEYMOD_SHIFT);
            break;
        case KEY_DEL:
            CommandDelete();
            break;
        case KEY_BACKSPACE:
            CommandBackspace();
            break;
        case KEY_ENTER:
            CommandEnter();
            break;
        case KEY_TAB:
            if(lMod&KEYMOD_SHIFT)
                CommandExpand();
            else
                CommandComplete();
            break;
        case KEY_0:
        case KEY_1:
        case KEY_2:
        case KEY_3:
        case KEY_4:
        case KEY_5:
        case KEY_6:
        case KEY_7:
        case KEY_8:
        case KEY_9:
            if(lMod&KEYMOD_ALT)
                CommandNumArg(lCode-KEY_0);
            break;
        case KEY_A:
            if(lMod&KEYMOD_CONTROL)
                CommandHome(false);
            else if(lMod&KEYMOD_ALT)
                CommandHome(true);
            break;
        case KEY_B:
            if(lMod&KEYMOD_CONTROL)
                CommandCharLeft(false);
            else if(lMod&KEYMOD_ALT)
                CommandCharLeft(true);
            break;
        case KEY_C:
            if(lMod&KEYMOD_CONTROL)
                CommandCancel();
            break;
        case KEY_D:
            if(lMod&KEYMOD_CONTROL)
                CommandEOF();
            break;
        case KEY_E:
            if(lMod&KEYMOD_CONTROL)
                CommandEnd(false);
            else if(lMod&KEYMOD_ALT)
                CommandEnd(true);
            break;
        case KEY_F:
            if(lMod&KEYMOD_CONTROL)
                CommandCharRight(false);
            else if(lMod&KEYMOD_ALT)
                CommandCharRight(true);
            break;
        case KEY_J:
            if(lMod&KEYMOD_CONTROL)
                CommandEnter();
            break;
        case KEY_K:
            if(lMod&KEYMOD_CONTROL)
                CommandRemoveToEOL();
            break;
        case KEY_N:
            CommandHistoryForward();
            break;
        case KEY_P:
            CommandHistoryBack();
            break;
        case KEY_R:
            if(lMod&KEYMOD_CONTROL)
                CommandSearch();
            break;
        case KEY_T:
            if(lMod&KEYMOD_CONTROL)
                CommandRegex();
            break;
        case KEY_U:
            if(lMod&KEYMOD_CONTROL)
                CommandUsual();
            break;
        case KEY_V:
            if(lMod&KEYMOD_CONTROL)
                CommandPasteLine();
            break;
        case KEY_X:
            if(lMod&KEYMOD_CONTROL)
                CommandCutLine();
            break;
        default:
            break;
        }
    }
    else if(pCh>=32)
    {
        CommandInsertChar(pCh);
    }
}

std::wstring CLineReader::ReadWLine(const std::wstring &pStart)
{
    //TODO:two optimizations:
    //         -redraw only some part if possible
    //         -try to process more input at the same time if possible
    mIO.Start();

    mMode=MODE_NORMAL;
    mHistory.mPos=mHistory.mLines.size();
    
    mText=pStart;
    if(mText.empty()&&!mIndentPositions.empty())
    {
        mText.append(mIndentPositions.back(),L' ');
    }
    mCursor=mText.length();
    mSelect.mB=0;
    mSelect.mE=0;
    mTextEndPos.Set(0,0);
    mNumArg=1;

    RecalcAndRedraw();
    
    mFinishLine=false;
    while(!mFinishLine)
    {
        ChangedNone();
    
        wchar_t lCh=mIO.GetWChar();

        if(lCh==0)
        {
            //this will happen at EOF and in case of error, which is strange.
            mEOF=true;
            mIO.MoveCursor(mTextEndPos);
            break;
        }

        if(mMode&MODE_MESSAGE)
        {
            mMode&=~MODE_MESSAGE;
            Changed(CHANGE_MESSAGE);
        }

        ProcessChar(lCh);

        if(mChanged.mType&CHANGE_PROMPT)
        {
            RecalcPrompt();
            mChanged.mFrom=-1;
        }
        if(mChanged.mType&(CHANGE_TEXT|CHANGE_PROMPT))
        {
            RecalcText();
        }
        if(mChanged.mType&(CHANGE_PROMPT|CHANGE_COMPLETE|CHANGE_MESSAGE|CHANGE_TEXT|CHANGE_SELECTION|CHANGE_NUMARG|
                           CHANGE_SEARCHPOS))
        {
            Redraw(mChanged.mFrom);
        }
        else if(mChanged.mType&CHANGE_CURSOR)
        {
            mIO.MoveCursor(GetCursorPos(mCursor));
        } 
    }
    
    mIO.NewLine();
    mIO.ClearToEnd();
    mIO.End();
    return mText;
}

void CLineReader::LoadHistory(const io::CURI &pURI)
{
    io::PBufferStream lStream=io::open_buffered(pURI,io::EMode::READ);
    
    std::wstring lLine;
    
    while(get_line(lStream,lLine))
    {
        if(!lLine.empty())
            mHistory.mLines.push_back(lLine);
    }
    
    mHistory.mFirstNew=mHistory.mLines.size();
}
    
void CLineReader::SaveHistory(const io::CURI &pURI)
{
    if(mHistory.mFirstNew>=mHistory.mLines.size()) return;
    
    io::CTextWriter lWriter(pURI);///\todo make this be appending if it's not by default
    
    for(int i=mHistory.mFirstNew;i!=mHistory.mLines.size();i++)
        if(mHistory.mLines[i].length()<1024)
            lWriter << mHistory.mLines[i] << "\n";
}

void CLineReader::AddToHistory(const std::wstring &pText)
{
    if(mHistory.mLines.empty()||pText!=mHistory.mLines.back())
        mHistory.mLines.push_back(pText);
}
    
/*namespace tty*/ } /*namespace buola*/ }
